using System.Collections.Concurrent;
using System.Drawing;
using Microsoft.Extensions.Logging;

namespace _3Dto2D.Services;

public interface IObjectPool<T> : IDisposable where T : class
{
    T Rent();
    void Return(T item);
    int AvailableCount { get; }
    int TotalCount { get; }
}

public class ObjectPool<T> : IObjectPool<T> where T : class
{
    private readonly ConcurrentQueue<T> _pool = new();
    private readonly Func<T> _factory;
    private readonly Action<T>? _resetAction;
    private readonly int _maxSize;
    private int _totalCount;
    private bool _disposed;

    public int AvailableCount => _pool.Count;
    public int TotalCount => _totalCount;

    public ObjectPool(Func<T> factory, Action<T>? resetAction = null, int maxSize = 100)
    {
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
        _resetAction = resetAction;
        _maxSize = maxSize;
    }

    public T Rent()
    {
        if (_disposed)
            throw new ObjectDisposedException(nameof(ObjectPool<T>));

        if (_pool.TryDequeue(out var item))
        {
            return item;
        }

        Interlocked.Increment(ref _totalCount);
        return _factory();
    }

    public void Return(T item)
    {
        if (_disposed || item == null)
            return;

        _resetAction?.Invoke(item);

        if (_pool.Count < _maxSize)
        {
            _pool.Enqueue(item);
        }
        else
        {
            // Pool is full, dispose if possible
            if (item is IDisposable disposable)
            {
                disposable.Dispose();
            }
            Interlocked.Decrement(ref _totalCount);
        }
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        _disposed = true;

        while (_pool.TryDequeue(out var item))
        {
            if (item is IDisposable disposable)
            {
                disposable.Dispose();
            }
        }

        _totalCount = 0;
    }
}

public class BitmapPool : IObjectPool<Bitmap>
{
    private readonly ObjectPool<Bitmap> _pool;
    private readonly ILogger<BitmapPool> _logger;

    public int AvailableCount => _pool.AvailableCount;
    public int TotalCount => _pool.TotalCount;

    public BitmapPool(ILogger<BitmapPool> logger, int width = 250, int height = 250, int maxSize = 50)
    {
        _logger = logger;
        _pool = new ObjectPool<Bitmap>(
            factory: () => new Bitmap(width, height),
            resetAction: ResetBitmap,
            maxSize: maxSize
        );
    }

    public Bitmap Rent()
    {
        var bitmap = _pool.Rent();
        _logger.LogTrace("Rented bitmap from pool. Available: {Available}, Total: {Total}", 
            AvailableCount, TotalCount);
        return bitmap;
    }

    public void Return(Bitmap bitmap)
    {
        _pool.Return(bitmap);
        _logger.LogTrace("Returned bitmap to pool. Available: {Available}, Total: {Total}", 
            AvailableCount, TotalCount);
    }

    private static void ResetBitmap(Bitmap bitmap)
    {
        // Clear the bitmap by filling it with transparent pixels
        using var graphics = Graphics.FromImage(bitmap);
        graphics.Clear(Color.Transparent);
    }

    public void Dispose()
    {
        _pool.Dispose();
    }
}

public class MemoryStreamPool : IObjectPool<MemoryStream>
{
    private readonly ObjectPool<MemoryStream> _pool;

    public int AvailableCount => _pool.AvailableCount;
    public int TotalCount => _pool.TotalCount;

    public MemoryStreamPool(int maxSize = 20)
    {
        _pool = new ObjectPool<MemoryStream>(
            factory: () => new MemoryStream(),
            resetAction: ResetStream,
            maxSize: maxSize
        );
    }

    public MemoryStream Rent()
    {
        return _pool.Rent();
    }

    public void Return(MemoryStream stream)
    {
        _pool.Return(stream);
    }

    private static void ResetStream(MemoryStream stream)
    {
        stream.SetLength(0);
        stream.Position = 0;
    }

    public void Dispose()
    {
        _pool.Dispose();
    }
}
